package io.hepu.robotize.exception;

import io.hepu.robotize.enums.IStatus;
import io.hepu.robotize.http.HttpResponse;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.util.ArrayList;
import java.util.List;

/**
 * Global exceptions handler
 */
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionResolver {
    private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionResolver.class);

    /**
     * 校验异常
     */
    @ExceptionHandler({ MethodArgumentNotValidException.class, ConstraintViolationException.class })
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public HttpResponse<Object> handleValidException(final Exception e, HttpServletRequest req, @RequestBody String body) {
        List<String> errors = new ArrayList<>();
        if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException methodException = (MethodArgumentNotValidException) e;
            methodException.getBindingResult().getFieldErrors().forEach(error -> errors.add(error.getDefaultMessage()));
        } else if (e instanceof ConstraintViolationException) {
            ConstraintViolationException constraintException = (ConstraintViolationException) e;
            constraintException.getConstraintViolations().forEach(error -> errors.add(error.getMessage()));
        }
        log(req, "ValidException", errors.toString());
        return HttpResponse.of(IStatus.INTERNAL_SERVER_ERROR, "请求校验异常");
    }

    /**
     * 数据库操作异常
     */
    @ExceptionHandler(DataAccessException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public HttpResponse<Throwable> handleSQLException(final DataAccessException e) {
        LOG.error("### SQLException: {}", e.getCause().getMessage());
        return HttpResponse.of(IStatus.DB_ACCESS_ERROR);
    }

    /**
     * 数据库记录重复
     */
    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseStatus(HttpStatus.CONFLICT)
    public HttpResponse<Throwable> handleException(final DuplicateKeyException e) {
        LOG.error("### DuplicateKeyException: {}", e.getCause().getMessage());
        return HttpResponse.of(IStatus.DUPLICATE_KEY);
    }

    /**
     * 权限不足异常
     */
    @ExceptionHandler(UnauthorizedException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public HttpResponse<Throwable> handleException(final UnauthorizedException e) {
        return HttpResponse.of(IStatus.AUTHORIZED_DENIED);
    }

    /**
     * 身份验证异常
     */
    @ExceptionHandler(UnauthenticatedException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public HttpResponse<Throwable> handleException(final UnauthenticatedException e) {
        return HttpResponse.of(IStatus.AUTHENTICATED_FAILURE);
    }

    /**
     * 身份验证异常
     */
    @ExceptionHandler(AuthGrantException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public HttpResponse<Object> handleException(final AuthGrantException e) {
        LOG.error("### AuthGrantException：{}", e.getMessage());
        return HttpResponse.of(e.getCode(), e.getMessage());
    }

    /**
     * 404异常
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public HttpResponse<Throwable> handleException(HttpServletRequest req, final NoHandlerFoundException e) {
        log(req, "NoHandlerFoundException", e.getMessage());
        return HttpResponse.of(IStatus.REQ_NOT_FOUND);
    }

    /**
     * 请求频率限制
     */
    @ExceptionHandler(RequestLimitException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public HttpResponse<Object> handleException(HttpServletRequest req, final RequestLimitException e) {
        log(req, "RequestLimitException", e.getMessage());
        return HttpResponse.of(IStatus.FORBIDDEN, e.getMessage());
    }

    /**
     * 全局异常
     */
    @ExceptionHandler(GlobalException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public HttpResponse<Object> handleException(HttpServletRequest req, final GlobalException e) {
        log(req, "GlobalException", e.getMessage());
        return HttpResponse.of(e.getCode(), e.getMessage());
    }

    /**
     * 未捕获异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public HttpResponse<Throwable> handleException(HttpServletRequest req, final Exception e) {
        log(req, "Exception", e.getCause().getMessage());
        e.printStackTrace();
        return HttpResponse.of(IStatus.INTERNAL_SERVER_ERROR);
    }

    private void log(HttpServletRequest req, String type, String message) {
        if (req != null) {
            LOG.error("### {}: {} :: Request uri={}, method={}", type, message, req.getRequestURI(), req.getMethod());
        }
    }
}
